package org.stars.config;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Set;

import org.stars.util.StringUtility;

/**
 * Contiene le variabili definite a livello d'applicazione.
 * <p>
 * Riportiamo un esempio di codice per accedere alle variabili contenute nella
 * classe:
 * 
 * <pre>
 * <code>
 * ... 
 * // recuperiamo istanza classe AppSettings
 * AppSettings app=AppSettings.getInstance();
 * 
 * // recuperiamo valore della variabile ciao	
 * String val=app.parameter("ciao");
 * ...		
 * </code>
 * </pre>
 * <p>
 * Per inserire una nuova variabile all'interno dell'instanza di
 * <code>AppSettings</code> si deve utilizzare il metodo
 * {@link #setParameter(String, Object)} Ogni parametro viene identificato da
 * una chiave stringa la cui chiave è <code>CASE INSENSITIVE</code>. Non possono
 * essere inseriti parametri con valori <code>null</code>; se si prova ad
 * inserire un parametro con valore, esso semplicemente non viene inserito.
 * <p>
 * Ci sono alcuni parametri la cui chiave viene definita a priori. Questi
 * parametri sono:
 * <ul>
 * <li>
 * <code>PATH</code> rappresenta il <code>path</code> dell'applicazione. La
 * chiave predefinita è <code>path</code>. Essa è stata definita nella costante
 * a livello di classe <code>PATH</code></li>.
 * <li>
 * <code>CONTEXT_PATH</code> rappresenta il <code>context-path</code>
 * dell'applicazione web. La chiave predefinita è <code>context-path</code> ed
 * definita a livello di classe dalla costante <code>CONTEXT_PATH</code>.</li>
 * </ul>
 * <p>
 * <h3>Dettagli implementativi</h3>
 * <p>
 * Questa classe implementa il pattern <code>singleton</code>.
 * <p>
 * I parametri sono memorizzati in una hashtable interna. Si è optato per
 * l'hashtable piuttosto che sul un oggetto di tipo <code>Properties</code> per
 * permettere eventualmente un giorno di supportare dei tipi di proprietà
 * diversi dalle stringhe.
 * <p>
 * Le chiavi vengono memorizzate in UPPERCASE e i controlli all'interno di
 * questa classe vengono comunque fatti in UPPERCASE (le chiavi sono CASE
 * INSENTIVE).
 * <p>
 * 
 * @author Francesco Benincasa (908099)
 */
public class AppSettings implements Serializable {

	// Private constructor prevents instantiation from other classes
	protected AppSettings() {
		m_hParameters = new HashMap<String, Object>();
	}

	/**
	 * SingletonHolder is loaded on the first execution of
	 * Singleton.getInstance() or the first access to SingletonHolder.INSTANCE,
	 * not before.
	 */
	private static class AppSettingsHolder {
		public static final AppSettings INSTANCE = new AppSettings();
	}

	public static AppSettings getInstance() {
		return AppSettingsHolder.INSTANCE;
	}

	/**
	 * path fisico dell'applicazione
	 */
	public final static String PATH = "PATH";

	/**
	 * context path dell'applicazione web
	 */
	public final static String CONTEXT_PATH = "CONTEXT-PATH";

	/**
	 * Recupera il path fisico dell'applicazione
	 * 
	 * @return path fisico dell'applicazione
	 */
	public String getPath() {
		return parameter(PATH);
	}

	/**
	 * Recupera il parametro di default che contiene il context-path
	 * dell'applicazione.
	 * 
	 * @return context-path dell'applicazione
	 */
	public String getContextPath() {
		return parameter(CONTEXT_PATH);
	}

	/**
	 * Imposta il parametro di default che contiene il context-path
	 * dell'applicazione.
	 * 
	 * @param value
	 *            nuovo valore del context-path
	 */
	public void setContextPath(String value) {
		putParameter(CONTEXT_PATH, value);
	}

	/**
	 * Imposta il parametro di default che contiene il path dell'applicazione.
	 * 
	 * @param value
	 *            nuovo valore
	 * 
	 */
	public void setPath(String value) {
		putParameter(PATH, value);
	}

	/**
	 * serial id
	 */
	private static final long serialVersionUID = -6778659326341489194L;

	/**
	 * Recupera i parametri sottoforma di hashtable.
	 * 
	 * @return Hasbtable delle proprietà.
	 */
	public HashMap<String, Object> getParameters() {
		return m_hParameters;
	}

	/**
	 * Imposta i parametri mediante un'hashtable. Per sicurezza viene creata una
	 * nuova hashtable nella quale vengono definite tutte le chiavi passate in
	 * <code>uppercase</code>. L'hashtable non può essere <code>null</code>; in
	 * tal caso non viene
	 * 
	 * @param values
	 *            Parametri in input.
	 */
	public void setParameters(HashMap<String, Object> values) {
		if (values == null) {
			m_hParameters.clear(); // puliamo la vecchia hashtable
			return;
		}
		HashMap<String, Object> val = new HashMap<String, Object>();

		for (String key : values.keySet()) {
			val.put(key.toUpperCase(), values.get(key));
		}

		m_hParameters = val;
	}

	/**
	 * Imposta un parametro fornendo una chiave ed un valore. Non è possibile
	 * inserire una chiave o un valore <code>null</code>.
	 * 
	 * @param key
	 *            identificativo proprietà
	 * @param value
	 *            valore proprietà
	 */
	public void putParameter(String key, String value) {
		m_hParameters.put(key.toUpperCase(), value);
	}

	/**
	 * Recupera un parametro mediante la sua chiave. Nel caso in cui il
	 * parametro richiesto non esiste, viene restituito <code>null</code>.
	 * 
	 * @param key
	 *            Chiave del parametro da recuperare.
	 * 
	 * @return Restituisce una stringa con il valore del parametro.
	 */
	public String parameter(String key) {
		return parameter(key, null);
	}

	/**
	 * Recupera un parametro strigna mediante la sua chiave. Nel caso in cui il
	 * parametro richiesto non esiste, viene restituito il valore di default.
	 * 
	 * @param key
	 *            chiave del parametro da recuperare
	 * @param defaultValue
	 *            valore di default
	 * @return stringa contenente o il valore del parametro o il valore di
	 *         default
	 */
	public String parameter(String key, String defaultValue) {
		if (key == null)
			return defaultValue;

		key = key.toUpperCase();
		if (m_hParameters.containsKey(key))
			return (String) m_hParameters.get(key);
		else
			return defaultValue;
	}

	/**
	 * Recupera un parametro mediante la sua chiave. Nel caso in cui il
	 * parametro richiesto non esiste, viene restituito il valore
	 * <code>null</code>. L'oggetto che viene restituito è di tipo
	 * <code>object</code>. Questo metodo si differenzia dal metodo
	 * <code>parameter</code> per il fatto che restituisce un
	 * <code>Object</code> e non una stringa
	 * 
	 * @param key
	 *            chiave del parametro
	 * @return valore del parametro sottoforma di object
	 */
	public Object getParameter(String key) {
		return getParameter(key, null);
	}

	/**
	 * Recupera un parametro mediante la sua chiave. Nel caso in cui il
	 * parametro richiesto non esiste, viene restituito il valore di
	 * <code>default</code>.
	 * 
	 * @param key
	 *            chiave del parametro
	 * @param defaultValue
	 *            valore di default del parametro
	 * @return valore del parametro sottoforma di object
	 */
	public Object getParameter(String key, Object defaultValue) {
		if (key == null) {
			return defaultValue;
		}

		key = key.toUpperCase();
		if (m_hParameters.containsKey(key))
			return m_hParameters.get(key);
		else
			return defaultValue;
	}

	/**
	 * Imposta un parametro fornendo una chiave ed un valore. Non è possibile
	 * inserire una chiave o un valore <code>null</code>.
	 * 
	 * @param key
	 *            identificativo proprietà
	 * @param value
	 *            valore proprietà
	 */
	public void setParameter(String key, Object value) {
		m_hParameters.put(key.toUpperCase(), value);
	}

	/**
	 * Data una stringa <code>sOldString</code>, ricerca in essa tutte le chiavi
	 * dei parametri definiti in <code>AppSettings</code> preceduti e sostituiti
	 * dal carattere <code>%</code> e li sostituisce con i relativi valori. Se
	 * viene passata una stringa vuota, viene restituita una stringa vuota.
	 * <p>
	 * Ad esempio, se in <code>AppSettings</code> abbiamo definito una variabile
	 * <code>NOME=pluto</code> abbiamo:
	 * 
	 * <pre>
	 * sOldString = &quot;ciao %NOME%&quot;;
	 * sNewString = AppSettings.getInstance().replace(sOldString);
	 * System.out.println(&quot;sNewString=\&quot;&quot; + sNewString + &quot;\&quot;&quot;);
	 * </pre>
	 * 
	 * Il risultato a video sarà:
	 * 
	 * <pre>
	 * sNewString = &quot;ciao pluto&quot;
	 * </pre>
	 * 
	 * @param sOldString
	 *            stringa da elaborare
	 * @return stringa con i parametri sostituiti dai relativi valori.
	 */
	public String replace(String sOldString) {
		if (sOldString == null) {
			return null;
		}

		String sAppKey = "";
		String sAppValue = "";

		for (String key : m_hParameters.keySet()) {
			sAppKey = key.toUpperCase();
			sAppValue = (String) m_hParameters.get(sAppKey);
			sOldString = StringUtility.replace(sOldString, "%" + sAppKey + "%", sAppValue);
		}

		return sOldString;
	}

	/**
	 * Cerca in tutti i parametri di <code>AppSettings</code> la stringa
	 * definita mediante il parametro <code>sOldValue</code> e la sostituisce
	 * con la stringa <code>sNewValue</code>. <br>
	 * La sostituzione è <code>case sensitive</code>.
	 * 
	 * @param sOldValue
	 *            Stringa da ricercare
	 * @param sNewValue
	 *            Stringa da inserire
	 */
	public void replaceInParameters(String sOldValue, String sNewValue) {
		HashMap<String, Object> tavola = new HashMap<String, Object>();

		Object temp;
		String sAppValue = "";
		Set<String> key = m_hParameters.keySet();

		for (String item : key) {
			temp = m_hParameters.get(item);

			if (temp instanceof String) {
				sAppValue = (String) temp;
				sAppValue = StringUtility.replace(sAppValue, sOldValue, sNewValue);
				tavola.put(item, sAppValue);
			}
		}

		m_hParameters = tavola;
	}

	/**
	 * Parametri.
	 */
	protected HashMap<String, Object> m_hParameters;

	/**
	 * Istanza della classe.
	 */
	protected static AppSettings m_Instance;
}